package com.android.fxcontacts;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Locale;

import com.android.internal.telephony.FxTelephony;
import com.android.internal.telephony.ITelephony;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.content.ActivityNotFoundException;
import android.content.AsyncQueryHandler;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.database.CharArrayBuffer;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.database.sqlite.SQLiteDiskIOException;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteFullException;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Intents.Insert;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

public class RecentCallsListActivity extends ListActivity implements
		View.OnCreateContextMenuListener
{
	private static final String TAG = "RecentCallsList";

	/** The projection to use when querying the call log table */
	static final String[] CALL_LOG_PROJECTION = new String[] { Calls._ID,
			Calls.NUMBER, Calls.DATE, Calls.DURATION, Calls.TYPE,
			Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE,
			Calls.CACHED_NUMBER_LABEL };

	static final int ID_COLUMN_INDEX = 0;
	static final int NUMBER_COLUMN_INDEX = 1;
	static final int DATE_COLUMN_INDEX = 2;
	static final int DURATION_COLUMN_INDEX = 3;
	static final int CALL_TYPE_COLUMN_INDEX = 4;
	static final int CALLER_NAME_COLUMN_INDEX = 5;
	static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 6;
	static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 7;

	/** The projection to use when querying the phones table */
	static final String[] PHONES_PROJECTION = new String[] { PhoneLookup._ID,
			PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE, PhoneLookup.LABEL,
			PhoneLookup.NUMBER };

	static final int PERSON_ID_COLUMN_INDEX = 0;
	static final int NAME_COLUMN_INDEX = 1;
	static final int PHONE_TYPE_COLUMN_INDEX = 2;
	static final int LABEL_COLUMN_INDEX = 3;
	static final int MATCHED_NUMBER_COLUMN_INDEX = 4;

	private static final int MENU_ITEM_DELETE = 1;
	private static final int MENU_ITEM_DELETE_ALL = 2;
	private static final int MENU_ITEM_VIEW_CONTACTS = 3;

	private static final int QUERY_TOKEN = 53;
	private static final int UPDATE_TOKEN = 54;

	private static final int DIALOG_CONFIRM_DELETE_ALL = 1;

	RecentCallsAdapter mAdapter;
	private QueryHandler mQueryHandler;
	String mVoiceMailNumber;

	static final class ContactInfo
	{
		public long personId;
		public String name;
		public int type;
		public String label;
		public String number;
		public String formattedNumber;

		public static ContactInfo EMPTY = new ContactInfo();
	}

	public static final class RecentCallsListItemViews
	{
		TextView line1View;
		TextView labelView;
		TextView numberView;
		TextView dateView;
		ImageView iconView;
		View callView;
		ImageView groupIndicator;
		TextView groupSize;
	}

	static final class CallerInfoQuery
	{
		String number;
		int position;
		String name;
		int numberType;
		String numberLabel;
	}

	/**
	 * Shared builder used by {@link #formatPhoneNumber(String)} to minimize
	 * allocations when formatting phone numbers.
	 */
	private static final SpannableStringBuilder sEditable = new SpannableStringBuilder();

	/**
	 * Invalid formatting type constant for {@link #sFormattingType}.
	 */
	private static final int FORMATTING_TYPE_INVALID = -1;

	/**
	 * Cached formatting type for current {@link Locale}, as provided by
	 * {@link PhoneNumberUtils#getFormatTypeForLocale(Locale)}.
	 */
	private static int sFormattingType = FORMATTING_TYPE_INVALID;

	/** Adapter class to fill in data for the Call Log */
	final class RecentCallsAdapter extends GroupingListAdapter implements
			Runnable, ViewTreeObserver.OnPreDrawListener, View.OnClickListener
	{
		HashMap<String, ContactInfo> mContactInfo;
		private final LinkedList<CallerInfoQuery> mRequests;
		private volatile boolean mDone;
		private boolean mLoading = true;
		ViewTreeObserver.OnPreDrawListener mPreDrawListener;
		private static final int REDRAW = 1;
		private static final int START_THREAD = 2;
		private boolean mFirst;
		private Thread mCallerIdThread;

		private CharSequence[] mLabelArray;

		private Drawable mDrawableIncoming;
		private Drawable mDrawableOutgoing;
		private Drawable mDrawableMissed;

		/**
		 * Reusable char array buffers.
		 */
		private CharArrayBuffer mBuffer1 = new CharArrayBuffer(128);
		private CharArrayBuffer mBuffer2 = new CharArrayBuffer(128);

		public void onClick(View view)
		{
			String number = (String) view.getTag();
			if (!TextUtils.isEmpty(number))
			{
				Uri telUri = Uri.fromParts("tel", number, null);
				startActivity(new Intent(
						ContactsActivity.ACTION_CALL_PRIVILEGED, telUri));
			}
		}

		public boolean onPreDraw()
		{
			if (mFirst)
			{
				mHandler.sendEmptyMessageDelayed(START_THREAD, 1000);
				mFirst = false;
			}
			return true;
		}

		private Handler mHandler = new Handler()
		{
			@Override
			public void handleMessage(Message msg)
			{
				switch (msg.what)
				{
				case REDRAW:
					notifyDataSetChanged();
					break;
				case START_THREAD:
					startRequestProcessing();
					break;
				}
			}
		};

		public RecentCallsAdapter()
		{
			super(RecentCallsListActivity.this);

			mContactInfo = new HashMap<String, ContactInfo>();
			mRequests = new LinkedList<CallerInfoQuery>();
			mPreDrawListener = null;

			mDrawableIncoming = getResources().getDrawable(
					R.drawable.ic_call_log_list_incoming_call);
			mDrawableOutgoing = getResources().getDrawable(
					R.drawable.ic_call_log_list_outgoing_call);
			mDrawableMissed = getResources().getDrawable(
					R.drawable.ic_call_log_list_missed_call);
			mLabelArray = getResources().getTextArray(
					android.R.array.phoneTypes);
		}

		/**
		 * Requery on background thread when {@link Cursor} changes.
		 */
		@Override
		protected void onContentChanged()
		{
			// Start async requery
			startQuery();
		}

		void setLoading(boolean loading)
		{
			mLoading = loading;
		}

		@Override
		public boolean isEmpty()
		{
			if (mLoading)
			{
				// We don't want the empty state to show when loading.
				return false;
			} else
			{
				return super.isEmpty();
			}
		}

		public ContactInfo getContactInfo(String number)
		{
			return mContactInfo.get(number);
		}

		public void startRequestProcessing()
		{
			mDone = false;
			mCallerIdThread = new Thread(this);
			mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
			mCallerIdThread.start();
		}

		public void stopRequestProcessing()
		{
			mDone = true;
			if (mCallerIdThread != null)
				mCallerIdThread.interrupt();
		}

		public void clearCache()
		{
			synchronized (mContactInfo)
			{
				mContactInfo.clear();
			}
		}

		private void updateCallLog(CallerInfoQuery ciq, ContactInfo ci)
		{
			// Check if they are different. If not, don't update.
			if (TextUtils.equals(ciq.name, ci.name)
					&& TextUtils.equals(ciq.numberLabel, ci.label)
					&& ciq.numberType == ci.type)
			{
				return;
			}
			ContentValues values = new ContentValues(3);
			values.put(Calls.CACHED_NAME, ci.name);
			values.put(Calls.CACHED_NUMBER_TYPE, ci.type);
			values.put(Calls.CACHED_NUMBER_LABEL, ci.label);

			try
			{
				RecentCallsListActivity.this.getContentResolver().update(
						Calls.CONTENT_URI, values,
						Calls.NUMBER + "='" + ciq.number + "'", null);
			} catch (SQLiteDiskIOException e)
			{
				Log.w(TAG, "Exception while updating call info", e);
			} catch (SQLiteFullException e)
			{
				Log.w(TAG, "Exception while updating call info", e);
			} catch (SQLiteDatabaseCorruptException e)
			{
				Log.w(TAG, "Exception while updating call info", e);
			}
		}

		private void enqueueRequest(String number, int position, String name,
				int numberType, String numberLabel)
		{
			CallerInfoQuery ciq = new CallerInfoQuery();
			ciq.number = number;
			ciq.position = position;
			ciq.name = name;
			ciq.numberType = numberType;
			ciq.numberLabel = numberLabel;
			synchronized (mRequests)
			{
				mRequests.add(ciq);
				mRequests.notifyAll();
			}
		}

		private boolean queryContactInfo(CallerInfoQuery ciq)
		{
			// First check if there was a prior request for the same number
			// that was already satisfied
			ContactInfo info = mContactInfo.get(ciq.number);
			boolean needNotify = false;
			if (info != null && info != ContactInfo.EMPTY)
			{
				return true;
			} else
			{
				Cursor phonesCursor = RecentCallsListActivity.this
						.getContentResolver().query(
								Uri.withAppendedPath(
										PhoneLookup.CONTENT_FILTER_URI, Uri
												.encode(ciq.number)),
								PHONES_PROJECTION, null, null, null);
				if (phonesCursor != null)
				{
					if (phonesCursor.moveToFirst())
					{
						info = new ContactInfo();
						info.personId = phonesCursor
								.getLong(PERSON_ID_COLUMN_INDEX);
						info.name = phonesCursor.getString(NAME_COLUMN_INDEX);
						info.type = phonesCursor
								.getInt(PHONE_TYPE_COLUMN_INDEX);
						info.label = phonesCursor.getString(LABEL_COLUMN_INDEX);
						info.number = phonesCursor
								.getString(MATCHED_NUMBER_COLUMN_INDEX);

						// New incoming phone number invalidates our formatted
						// cache. Any cache fills happen only on the GUI thread.
						info.formattedNumber = null;

						mContactInfo.put(ciq.number, info);
						// Inform list to update this item, if in view
						needNotify = true;
					}
					phonesCursor.close();
				}
			}
			if (info != null)
			{
				updateCallLog(ciq, info);
			}
			return needNotify;
		}

		/*
		 * Handles requests for contact name and number type
		 * 
		 * @see java.lang.Runnable#run()
		 */
		public void run()
		{
			boolean needNotify = false;
			while (!mDone)
			{
				CallerInfoQuery ciq = null;
				synchronized (mRequests)
				{
					if (!mRequests.isEmpty())
					{
						ciq = mRequests.removeFirst();
					} else
					{
						if (needNotify)
						{
							needNotify = false;
							mHandler.sendEmptyMessage(REDRAW);
						}
						try
						{
							mRequests.wait(1000);
						} catch (InterruptedException ie)
						{
							// Ignore and continue processing requests
						}
					}
				}
				if (ciq != null && queryContactInfo(ciq))
				{
					needNotify = true;
				}
			}
		}

		@Override
		protected void addGroups(Cursor cursor)
		{

			int count = cursor.getCount();
			if (count == 0)
			{
				return;
			}

			int groupItemCount = 1;

			CharArrayBuffer currentValue = mBuffer1;
			CharArrayBuffer value = mBuffer2;
			cursor.moveToFirst();
			cursor.copyStringToBuffer(NUMBER_COLUMN_INDEX, currentValue);
			int currentCallType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
			for (int i = 1; i < count; i++)
			{
				cursor.moveToNext();
				cursor.copyStringToBuffer(NUMBER_COLUMN_INDEX, value);
				boolean sameNumber = equalPhoneNumbers(value, currentValue);

				// Group adjacent calls with the same number. Make an exception
				// for the latest item if it was a missed call. We don't want
				// a missed call to be hidden inside a group.
				if (sameNumber && currentCallType != Calls.MISSED_TYPE)
				{
					groupItemCount++;
				} else
				{
					if (groupItemCount > 1)
					{
						addGroup(i - groupItemCount, groupItemCount, false);
					}

					groupItemCount = 1;

					// Swap buffers
					CharArrayBuffer temp = currentValue;
					currentValue = value;
					value = temp;

					// If we have just examined a row following a missed call,
					// make
					// sure that it is grouped with subsequent calls from the
					// same number
					// even if it was also missed.
					if (sameNumber && currentCallType == Calls.MISSED_TYPE)
					{
						currentCallType = 0; // "not a missed call"
					} else
					{
						currentCallType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
					}
				}
			}
			if (groupItemCount > 1)
			{
				addGroup(count - groupItemCount, groupItemCount, false);
			}
		}

		protected boolean equalPhoneNumbers(CharArrayBuffer buffer1,
				CharArrayBuffer buffer2)
		{

			// TODO add PhoneNumberUtils.compare(CharSequence, CharSequence) to
			// avoid
			// string allocation
			return PhoneNumberUtils.compare(new String(buffer1.data, 0,
					buffer1.sizeCopied), new String(buffer2.data, 0,
					buffer2.sizeCopied));
		}

		@Override
		protected View newStandAloneView(Context context, ViewGroup parent)
		{
			LayoutInflater inflater = (LayoutInflater) context
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			View view = inflater.inflate(R.layout.recent_calls_list_item,
					parent, false);
			findAndCacheViews(view);
			return view;
		}

		@Override
		protected void bindStandAloneView(View view, Context context,
				Cursor cursor)
		{
			bindView(context, view, cursor);
		}

		@Override
		protected View newChildView(Context context, ViewGroup parent)
		{
			LayoutInflater inflater = (LayoutInflater) context
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			View view = inflater.inflate(R.layout.recent_calls_list_child_item,
					parent, false);
			findAndCacheViews(view);
			return view;
		}

		@Override
		protected void bindChildView(View view, Context context, Cursor cursor)
		{
			bindView(context, view, cursor);
		}

		@Override
		protected View newGroupView(Context context, ViewGroup parent)
		{
			LayoutInflater inflater = (LayoutInflater) context
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			View view = inflater.inflate(R.layout.recent_calls_list_group_item,
					parent, false);
			findAndCacheViews(view);
			return view;
		}

		@Override
		protected void bindGroupView(View view, Context context, Cursor cursor,
				int groupSize, boolean expanded)
		{
			final RecentCallsListItemViews views = (RecentCallsListItemViews) view
					.getTag();
			int groupIndicator = expanded ? R.drawable.expander_ic_maximized
					: R.drawable.expander_ic_minimized;
			views.groupIndicator.setImageResource(groupIndicator);
			views.groupSize.setText("(" + groupSize + ")");
			bindView(context, view, cursor);
		}

		private void findAndCacheViews(View view)
		{

			// Get the views to bind to
			RecentCallsListItemViews views = new RecentCallsListItemViews();
			views.line1View = (TextView) view.findViewById(R.id.line1);
			views.labelView = (TextView) view.findViewById(R.id.label);
			views.numberView = (TextView) view.findViewById(R.id.number);
			views.dateView = (TextView) view.findViewById(R.id.date);
			views.iconView = (ImageView) view.findViewById(R.id.call_type_icon);
			views.callView = view.findViewById(R.id.call_icon);
			views.callView.setOnClickListener(this);
			views.groupIndicator = (ImageView) view
					.findViewById(R.id.groupIndicator);
			views.groupSize = (TextView) view.findViewById(R.id.groupSize);
			view.setTag(views);
		}

		public void bindView(Context context, View view, Cursor c)
		{
			final RecentCallsListItemViews views = (RecentCallsListItemViews) view
					.getTag();

			String number = c.getString(NUMBER_COLUMN_INDEX);
			String formattedNumber = null;
			String callerName = c.getString(CALLER_NAME_COLUMN_INDEX);
			int callerNumberType = c.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
			String callerNumberLabel = c
					.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);

			// Store away the number so we can call it directly if you click on
			// the call icon
			views.callView.setTag(number);

			// Lookup contacts with this number
			ContactInfo info = mContactInfo.get(number);
			if (info == null)
			{
				// Mark it as empty and queue up a request to find the name
				// The db request should happen on a non-UI thread
				info = ContactInfo.EMPTY;
				mContactInfo.put(number, info);
				enqueueRequest(number, c.getPosition(), callerName,
						callerNumberType, callerNumberLabel);
			} else if (info != ContactInfo.EMPTY)
			{ // Has been queried
				// Check if any data is different from the data cached in the
				// calls db. If so, queue the request so that we can update
				// the calls db.
				if (!TextUtils.equals(info.name, callerName)
						|| info.type != callerNumberType
						|| !TextUtils.equals(info.label, callerNumberLabel))
				{
					// Something is amiss, so sync up.
					enqueueRequest(number, c.getPosition(), callerName,
							callerNumberType, callerNumberLabel);
				}

				// Format and cache phone number for found contact
				if (info.formattedNumber == null)
				{
					info.formattedNumber = formatPhoneNumber(info.number);
				}
				formattedNumber = info.formattedNumber;
			}

			String name = info.name;
			int ntype = info.type;
			String label = info.label;
			// If there's no name cached in our hashmap, but there's one in the
			// calls db, use the one in the calls db. Otherwise the name in our
			// hashmap is more recent, so it has precedence.
			if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(callerName))
			{
				name = callerName;
				ntype = callerNumberType;
				label = callerNumberLabel;

				// Format the cached call_log phone number
				formattedNumber = formatPhoneNumber(number);
			}
			// Set the text lines and call icon.
			// Assumes the call back feature is on most of the
			// time. For private and unknown numbers: hide it.
			views.callView.setVisibility(View.VISIBLE);

			if (!TextUtils.isEmpty(name))
			{
				views.line1View.setText(name);
				views.labelView.setVisibility(View.VISIBLE);
				CharSequence numberLabel = Phone.getTypeLabel(context.getResources(),
						ntype, label);
				views.numberView.setVisibility(View.VISIBLE);
				views.numberView.setText(formattedNumber);
				if (!TextUtils.isEmpty(numberLabel))
				{
					views.labelView.setText(numberLabel);
					views.labelView.setVisibility(View.VISIBLE);
				} else
				{
					views.labelView.setVisibility(View.GONE);
				}
			} else
			{
				if (number.equals(CallDetailActivity.CALLERINFO_UNKNOWN_NUMBER))
				{
					number = getString(R.string.unknown);
					views.callView.setVisibility(View.INVISIBLE);
				} else if (number
						.equals(CallDetailActivity.CALLERINFO_PRIVATE_NUMBER))
				{
					number = getString(R.string.private_num);
					views.callView.setVisibility(View.INVISIBLE);
				} else if (number
						.equals(CallDetailActivity.CALLERINFO_PAYPHONE_NUMBER))
				{
					number = getString(R.string.payphone);
				} else if (PhoneNumberUtils.extractNetworkPortion(number)
						.equals(mVoiceMailNumber))
				{
					number = getString(R.string.voicemail);
				} else
				{
					// Just a raw number, and no cache, so format it nicely
					number = formatPhoneNumber(number);
				}

				views.line1View.setText(number);
				views.numberView.setVisibility(View.GONE);
				views.labelView.setVisibility(View.GONE);
			}

			long date = c.getLong(DATE_COLUMN_INDEX);

			// Set the date/time field by mixing relative and absolute times.
			int flags = DateUtils.FORMAT_ABBREV_RELATIVE;

			views.dateView.setText(DateUtils.getRelativeTimeSpanString(date,
					System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS,
					flags));

			if (views.iconView != null)
			{
				int type = c.getInt(CALL_TYPE_COLUMN_INDEX);
				// Set the icon
				switch (type)
				{
				case Calls.INCOMING_TYPE:
					views.iconView.setImageDrawable(mDrawableIncoming);
					break;

				case Calls.OUTGOING_TYPE:
					views.iconView.setImageDrawable(mDrawableOutgoing);
					break;

				case Calls.MISSED_TYPE:
					views.iconView.setImageDrawable(mDrawableMissed);
					break;
				}
			}

			// Listen for the first draw
			if (mPreDrawListener == null)
			{
				mFirst = true;
				mPreDrawListener = this;
				view.getViewTreeObserver().addOnPreDrawListener(this);
			}
		}
	}

	private static final class QueryHandler extends AsyncQueryHandler
	{
		private final WeakReference<RecentCallsListActivity> mActivity;

		/**
		 * Simple handler that wraps background calls to catch
		 * {@link SQLiteException}, such as when the disk is full.
		 */
		protected class CatchingWorkerHandler extends
				AsyncQueryHandler.WorkerHandler
		{
			public CatchingWorkerHandler(Looper looper)
			{
				super(looper);
			}

			@Override
			public void handleMessage(Message msg)
			{
				try
				{
					// Perform same query while catching any exceptions
					super.handleMessage(msg);
				} catch (SQLiteDiskIOException e)
				{
					Log.w(TAG, "Exception on background worker thread", e);
				} catch (SQLiteFullException e)
				{
					Log.w(TAG, "Exception on background worker thread", e);
				} catch (SQLiteDatabaseCorruptException e)
				{
					Log.w(TAG, "Exception on background worker thread", e);
				}
			}
		}

		@Override
		protected Handler createHandler(Looper looper)
		{
			// Provide our special handler that catches exceptions
			return new CatchingWorkerHandler(looper);
		}

		public QueryHandler(Context context)
		{
			super(context.getContentResolver());
			mActivity = new WeakReference<RecentCallsListActivity>(
					(RecentCallsListActivity) context);
		}

		@Override
		protected void onQueryComplete(int token, Object cookie, Cursor cursor)
		{
			final RecentCallsListActivity activity = mActivity.get();
			if (activity != null && !activity.isFinishing())
			{
				final RecentCallsListActivity.RecentCallsAdapter callsAdapter = activity.mAdapter;
				callsAdapter.setLoading(false);
				callsAdapter.changeCursor(cursor);
			} else
			{
				cursor.close();
			}
		}
	}

	@Override
	protected void onCreate(Bundle state)
	{
		super.onCreate(state);

		setContentView(R.layout.recent_calls);

		// Typing here goes to the dialer
		setDefaultKeyMode(DEFAULT_KEYS_DIALER);

		mAdapter = new RecentCallsAdapter();
		getListView().setOnCreateContextMenuListener(this);
		setListAdapter(mAdapter);

		mVoiceMailNumber = ((TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE))
				.getVoiceMailNumber();
		mQueryHandler = new QueryHandler(this);

		// Reset locale-based formatting cache
		sFormattingType = FORMATTING_TYPE_INVALID;
	}

	@Override
	protected void onResume()
	{
		// The adapter caches looked up numbers, clear it so they will get
		// looked up again.
		if (mAdapter != null)
		{
			mAdapter.clearCache();
		}

		startQuery();
		resetNewCallsFlag();

		super.onResume();

		mAdapter.mPreDrawListener = null; // Let it restart the thread after
		// next draw
	}

	@Override
	protected void onPause()
	{
		super.onPause();

		// Kill the requests thread
		mAdapter.stopRequestProcessing();
	}

	@Override
	protected void onDestroy()
	{
		super.onDestroy();
		mAdapter.stopRequestProcessing();
		mAdapter.changeCursor(null);
	}

	@Override
	public void onWindowFocusChanged(boolean hasFocus)
	{
		super.onWindowFocusChanged(hasFocus);

		// Clear notifications only when window gains focus. This activity won't
		// immediately receive focus if the keyguard screen is above it.
		if (hasFocus)
		{
			try
			{
//				ITelephony iTelephony = ITelephony.Stub
//						.asInterface(ServiceManager.getService("phone"));
				ITelephony iTelephony = FxTelephony.getITelelephony(this);
				if (iTelephony != null)
				{
					iTelephony.cancelMissedCallsNotification();
				} else
				{
					Log.w(TAG, "Telephony service is null, can't call "
							+ "cancelMissedCallsNotification");
				}
			} catch (RemoteException e)
			{
				Log
						.e(TAG,
								"Failed to clear missed calls notification due to remote exception");
			}
		}
	}

	/**
	 * Format the given phone number using
	 * {@link PhoneNumberUtils#formatNumber(android.text.Editable, int)}. This
	 * helper method uses {@link #sEditable} and {@link #sFormattingType} to
	 * prevent allocations between multiple calls.
	 * <p>
	 * Because of the shared {@link #sEditable} builder, <b>this method is not
	 * thread safe</b>, and should only be called from the GUI thread.
	 * <p>
	 * If the given String object is null or empty, return an empty String.
	 */
	private String formatPhoneNumber(String number)
	{
		if (TextUtils.isEmpty(number))
		{
			return "";
		}

		// Cache formatting type if not already present
		if (sFormattingType == FORMATTING_TYPE_INVALID)
		{
			sFormattingType = PhoneNumberUtils.getFormatTypeForLocale(Locale
					.getDefault());
		}

		sEditable.clear();
		sEditable.append(number);

		PhoneNumberUtils.formatNumber(sEditable, sFormattingType);
		return sEditable.toString();
	}

	private void resetNewCallsFlag()
	{
		// Mark all "new" missed calls as not new anymore
		StringBuilder where = new StringBuilder("type=");
		where.append(Calls.MISSED_TYPE);
		where.append(" AND new=1");

		ContentValues values = new ContentValues(1);
		values.put(Calls.NEW, "0");
		mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI,
				values, where.toString(), null);
	}

	private void startQuery()
	{
		mAdapter.setLoading(true);

		// Cancel any pending queries
		mQueryHandler.cancelOperation(QUERY_TOKEN);
		mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI,
				CALL_LOG_PROJECTION, null, null, Calls.DEFAULT_SORT_ORDER);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		menu.add(0, MENU_ITEM_DELETE_ALL, 0, R.string.recentCalls_deleteAll)
				.setIcon(android.R.drawable.ic_menu_close_clear_cancel);
		return true;
	}

	@Override
	public void onCreateContextMenu(ContextMenu menu, View view,
			ContextMenuInfo menuInfoIn)
	{
		AdapterView.AdapterContextMenuInfo menuInfo;
		try
		{
			menuInfo = (AdapterView.AdapterContextMenuInfo) menuInfoIn;
		} catch (ClassCastException e)
		{
			Log.e(TAG, "bad menuInfoIn", e);
			return;
		}

		Cursor cursor = (Cursor) mAdapter.getItem(menuInfo.position);

		String number = cursor.getString(NUMBER_COLUMN_INDEX);
		Uri numberUri = null;
		boolean isVoicemail = false;
		if (number.equals(CallDetailActivity.CALLERINFO_UNKNOWN_NUMBER))
		{
			number = getString(R.string.unknown);
		} else if (number.equals(CallDetailActivity.CALLERINFO_PRIVATE_NUMBER))
		{
			number = getString(R.string.private_num);
		} else if (number.equals(CallDetailActivity.CALLERINFO_PAYPHONE_NUMBER))
		{
			number = getString(R.string.payphone);
		} else if (PhoneNumberUtils.extractNetworkPortion(number).equals(
				mVoiceMailNumber))
		{
			number = getString(R.string.voicemail);
			numberUri = Uri.parse("voicemail:x");
			isVoicemail = true;
		} else
		{
			numberUri = Uri.fromParts("tel", number, null);
		}

		ContactInfo info = mAdapter.getContactInfo(number);
		boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY);
		if (contactInfoPresent)
		{
			menu.setHeaderTitle(info.name);
		} else
		{
			menu.setHeaderTitle(number);
		}

		if (numberUri != null)
		{
			Intent intent = new Intent(ContactsActivity.ACTION_CALL_PRIVILEGED,
					numberUri);
			menu.add(
					0,
					0,
					0,
					getResources().getString(R.string.recentCalls_callNumber,
							number)).setIntent(intent);
		}

		if (contactInfoPresent)
		{
			menu.add(0, 0, 0, R.string.menu_viewContact).setIntent(
					new Intent(Intent.ACTION_VIEW, ContentUris.withAppendedId(
							Contacts.CONTENT_URI, info.personId)));
		}

		if (numberUri != null && !isVoicemail)
		{
			menu.add(0, 0, 0, R.string.recentCalls_editNumberBeforeCall)
					.setIntent(new Intent(Intent.ACTION_DIAL, numberUri));
			menu.add(0, 0, 0, R.string.menu_sendTextMessage).setIntent(
					new Intent(Intent.ACTION_SENDTO, Uri.fromParts("sms",
							number, null)));
		}
		if (!contactInfoPresent && numberUri != null && !isVoicemail)
		{
			Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
			intent.setType(Contacts.CONTENT_ITEM_TYPE);
			intent.putExtra(Insert.PHONE, number);
			menu.add(0, 0, 0, R.string.recentCalls_addToContact).setIntent(
					intent);
		}
		menu.add(0, MENU_ITEM_DELETE, 0,
				R.string.recentCalls_removeFromRecentList);
	}

	@Override
	protected Dialog onCreateDialog(int id, Bundle args)
	{
		switch (id)
		{
		case DIALOG_CONFIRM_DELETE_ALL:
			return new AlertDialog.Builder(this).setTitle(
					R.string.clearCallLogConfirmation_title).setIcon(
					android.R.drawable.ic_dialog_alert).setMessage(
					R.string.clearCallLogConfirmation).setNegativeButton(
					android.R.string.cancel, null).setPositiveButton(
					android.R.string.ok, new OnClickListener()
					{
						public void onClick(DialogInterface dialog, int which)
						{
							getContentResolver().delete(Calls.CONTENT_URI,
									null, null);
							// TODO The change notification should do this
							// automatically, but it
							// isn't working right now. Remove this when the
							// change notification
							// is working properly.
							startQuery();
						}
					}).setCancelable(false).create();
		}
		return null;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		switch (item.getItemId())
		{
		case MENU_ITEM_DELETE_ALL:
		{
			showDialog(DIALOG_CONFIRM_DELETE_ALL);
			return true;
		}

		case MENU_ITEM_VIEW_CONTACTS:
		{
			Intent intent = new Intent(Intent.ACTION_VIEW, Contacts.CONTENT_URI);
			intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			startActivity(intent);
			return true;
		}
		}
		return super.onOptionsItemSelected(item);
	}

	@Override
	public boolean onContextItemSelected(MenuItem item)
	{
		// Convert the menu info to the proper type
		AdapterView.AdapterContextMenuInfo menuInfo;
		try
		{
			menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
		} catch (ClassCastException e)
		{
			Log.e(TAG, "bad menuInfoIn", e);
			return false;
		}

		switch (item.getItemId())
		{
		case MENU_ITEM_DELETE:
		{
			Cursor cursor = (Cursor) mAdapter.getItem(menuInfo.position);
			int groupSize = 1;
			if (mAdapter.isGroupHeader(menuInfo.position))
			{
				groupSize = mAdapter.getGroupSize(menuInfo.position);
			}

			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < groupSize; i++)
			{
				if (i != 0)
				{
					sb.append(",");
					cursor.moveToNext();
				}
				long id = cursor.getLong(ID_COLUMN_INDEX);
				sb.append(id);
			}

			getContentResolver().delete(Calls.CONTENT_URI,
					Calls._ID + " IN (" + sb + ")", null);
		}
		}
		return super.onContextItemSelected(item);
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event)
	{
		switch (keyCode)
		{
		case KeyEvent.KEYCODE_CALL:
		{
			long callPressDiff = SystemClock.uptimeMillis()
					- event.getDownTime();
			if (callPressDiff >= ViewConfiguration.getLongPressTimeout())
			{
				// Launch voice dialer
				Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
				intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
				try
				{
					startActivity(intent);
				} catch (ActivityNotFoundException e)
				{
				}
				return true;
			}
		}
		}
		return super.onKeyDown(keyCode, event);
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event)
	{
		switch (keyCode)
		{
		case KeyEvent.KEYCODE_CALL:
			try
			{
//				ITelephony phone = ITelephony.Stub.asInterface(ServiceManager
//						.checkService("phone"));
				ITelephony phone = FxTelephony.getITelelephony(this);
				if (phone != null && !phone.isIdle())
				{
					// Let the super class handle it
					break;
				}
			} catch (RemoteException re)
			{
				// Fall through and try to call the contact
			}

			callEntry(getListView().getSelectedItemPosition());
			return true;
		}
		return super.onKeyUp(keyCode, event);
	}

	/*
	 * Get the number from the Contacts, if available, since sometimes
	 * the number provided by caller id may not be formatted properly
	 * depending on the carrier (roaming) in use at the time of the
	 * incoming call.
	 * Logic : If the caller-id number starts with a "+", use it
	 * Else if the number in the contacts starts with a "+", use that one
	 * Else if the number in the contacts is longer, use that one
	 */
	private String getBetterNumberFromContacts(String number)
	{
		String matchingNumber = null;
		// Look in the cache first. If it's not found then query the Phones db
		ContactInfo ci = mAdapter.mContactInfo.get(number);
		if (ci != null && ci != ContactInfo.EMPTY)
		{
			matchingNumber = ci.number;
		} else
		{
			try
			{
				Cursor phonesCursor = RecentCallsListActivity.this
						.getContentResolver()
						.query(
								Uri.withAppendedPath(
										PhoneLookup.CONTENT_FILTER_URI, number),
								PHONES_PROJECTION, null, null, null);
				if (phonesCursor != null)
				{
					if (phonesCursor.moveToFirst())
					{
						matchingNumber = phonesCursor
								.getString(MATCHED_NUMBER_COLUMN_INDEX);
					}
					phonesCursor.close();
				}
			} catch (Exception e)
			{
				// Use the number from the call log
			}
		}
		if (!TextUtils.isEmpty(matchingNumber)
				&& (matchingNumber.startsWith("+") || matchingNumber.length() > number
						.length()))
		{
			number = matchingNumber;
		}
		return number;
	}

	private void callEntry(int position)
	{
		if (position < 0)
		{
			// In touch mode you may often not have something selected, so
			// just call the first entry to make sure that [send] [send] calls
			// the
			// most recent entry.
			position = 0;
		}
		final Cursor cursor = (Cursor) mAdapter.getItem(position);
		if (cursor != null)
		{
			String number = cursor.getString(NUMBER_COLUMN_INDEX);
			if (TextUtils.isEmpty(number)
					|| number
							.equals(CallDetailActivity.CALLERINFO_UNKNOWN_NUMBER)
					|| number
							.equals(CallDetailActivity.CALLERINFO_PRIVATE_NUMBER)
					|| number
							.equals(CallDetailActivity.CALLERINFO_PAYPHONE_NUMBER))
			{
				// This number can't be called, do nothing
				return;
			}

			int callType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
			if (!number.startsWith("+")
					&& (callType == Calls.INCOMING_TYPE || callType == Calls.MISSED_TYPE))
			{
				// If the caller-id matches a contact with a better qualified
				// number, use it
				number = getBetterNumberFromContacts(number);
			}
			Intent intent = new Intent(ContactsActivity.ACTION_CALL_PRIVILEGED,
					Uri.fromParts("tel", number, null));
			intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
					| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
			startActivity(intent);
		}
	}

	@Override
	protected void onListItemClick(ListView l, View v, int position, long id)
	{
		if (mAdapter.isGroupHeader(position))
		{
			mAdapter.toggleGroup(position);
		} else
		{
			Intent intent = new Intent(this, CallDetailActivity.class);
			intent.setData(ContentUris.withAppendedId(
					CallLog.Calls.CONTENT_URI, id));
			startActivity(intent);
		}
	}

	@Override
	public void startSearch(String initialQuery, boolean selectInitialQuery,
			Bundle appSearchData, boolean globalSearch)
	{
		if (globalSearch)
		{
			super.startSearch(initialQuery, selectInitialQuery, appSearchData,
					globalSearch);
		} else
		{
			ContactsSearchManager.startSearch(this, initialQuery);
		}
	}
}
